
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// This file contains the implementation for all the Global standard
// library stuff that can be implemented completely in javascript

// Ecma 15.1.1.1 NaN
// The initial value of NaN is NaN. This property has the attributes 
// {DontEnum, DontDelete}. 
// It turns out NaN is what you get doing 0/0.
Esiply.set_attributes(this, "NaN", Esiply.DONT_ENUM | Esiply.DONT_DELETE);
NaN = 0/0;

// Ecma 15.1.1.2 Infinity
// The intial value of Infinity is Infinity. The propert has the
// attributes {DontEnum, DontDelete}
// We can get Infinity with 1/0.
Esiply.set_attributes(this, "Infinity", Esiply.DONT_ENUM | Esiply.DONT_DELETE);
Infinity = 1/0;

// Ecma 15.1.1.3 undefined
// The initial value of undefined is defined to be undefined. This property
// has the attributes {DontEnum, DontDelete}.
// We can get undefined with the void operator.
Esiply.set_attributes(this, "undefined", Esiply.DONT_ENUM | Esiply.DONT_DELETE);
undefined = void 0;

// Ecma 15.1.2.1 eval(x)
// I haven't figured out how to implemented this in javascript yet. 
// IMPLEMENTED INTERNALLY

// Ecma 15.1.2.2 parseInt(string, radix)
// The parseInt function produces an integer value dictated by 
// interpretation of the contents of the string argument according
// to the specified radix. Leading whitespace in the string
// is ignored. If radix is undefined or 0, it is assumed to be 10 except
// when the number begins with the character pairs 0x or 0X, in which
// case a radix of 16 is assumed. Any radix-16 number may also optionally
// begin with the character pairs 0x or 0X.
Esiply.set_attributes(this, "parseInt", Esiply.DONT_ENUM);
parseInt = function(string, radix)
{
    // 1. Call toString(string)
    var str = new String(string);
    debug("parseInt: str: " + str + "\n");

    // 2. Remove leading whitespace.
    function isWhitespace(char)
    {
        return char == "\t" || char == " " || char == "\v" || char == "\f";
    }

    var i;
    for (i = 0; i < str.length && isWhitespace(str.charAt(i)); i++)
    {
        // Purposely empty
    }

    str = str.substring(i);

    // 3. Let sign be 1
    // 4. If str is not empty, and the first character of S is a minus sign,
    // let sign be -1
    var sign = (str && str.charAt(0) == "-") ? -1 : 1;

    // 5. If str is not empty, and the first character is + or -, remove
    // the first character
    if (str && (str.charAt(0) == "+" || str.charAt(0) == "-"))
    {
        str = str.substring(1);
    }

    // 6. Let r = toInt32(radix)
    // TODO: there must be a better way of doing this
    var r = Number(radix) << 0;

    // The steps are a little awkward from here on out, but I think
    // this covers it
    if (r == 0)
    {
        r = 10;
    }
    else if (r < 2 || r > 36)
    {
        return NaN;
    }

    if (r == 10 || r == 16)
    {
        // 13. If the length of S is at least 2, and the first two characters
        // of S are either "0x" or "0X", then remove the first two characters
        // of S and let r = 16
        if (str.length >= 2
                && (str.substring(0,2) == "0x" || str.substring(0,2) == "0X"))
        {
            str = str.substring(2);
            r = 16;
        }
    }

    // 14. If s contains any character that is not a radix-R digit, then
    // let z be the substring of S consisting of
    // all characters before the first such character, otherwise, let z be
    // s. In other words, chop the string past the first occurance of an
    // illegal character
    zero = "0".charCodeAt(0);
    a = "a".charCodeAt(0);
    A = "A".charCodeAt(0);

    function isValidChar(charcode, radix)
    {

        if (radix <= 10)
        {
            return charcode >= zero && charcode < zero + radix;
        }
        else if (radix > 10)
        {
            if (charcode >= zero && charcode < zero + 10)
            {
                return true;
            }
            if (charcode >= a && charcode < a + (radix-10))
            {
                return true;
            }
            return charcode >= A && charcode < A + (radix-10);
        }
        else
        {
            throw RangeError("radix not less than, equal to, or greater than 10");
        }
    }

    for (i = 0; i < str.length; i++)
    {
        if (!isValidChar(str.charCodeAt(i), r))
        {
            break;
        }
    }
    str = str.substring(0, i);

    // 15. If str is empty, return NaN
    if (!str)
    {
        return NaN;
    }

    // 16. Compute the integer value
    var res = 0;
    for (i = 0; i < str.length; i++)
    {
        res *= r;
        if (str.charCodeAt(i) < zero + 10)
        {
            res += str.charCodeAt(i)-zero;
        }
        else if (str.charCodeAt(i) >= a && str.charCodeAt(i) <= a + 26)
        {
            res += str.charCodeAt(i) - a + 10;
        }
        else
        {
            res += str.charCodeAt(i) - A + 10;
        }
    }

    return sign * res;
};

// Ecma 15.1.2.3 parseFloat(string)
// TODO: Implement this.
Esiply.set_attributes(this, "parseFloat", Esiply.DONT_ENUM);
parseFloat = function(string)
{
    throw new Error("parseFloat: Not Implemented Yet");
};

// Ecma 15.1.2.4 isNaN(number)
// Applies ToNumber to its argument, then returns true if the result
// is NaN, and otherwise returns false.
Esiply.set_attributes(this, "isNaN", Esiply.DONT_ENUM);
isNaN = function(number)
{
    // Convert to a number
    number = +number;

    // NaN is the only number not equal to itself
    return number != number;
};

// Ecma 15.1.2.5 isFinite(number)
// Applies ToNumber to its argument, then returns false if the result is
// NaN, +Infinity, or -Infinity, and otherwise returns true
Esiply.set_attributes(this, "isFinite", Esiply.DONT_ENUM);
isFinite = function(number)
{
    number = +number;
    return !(isNaN(number) || number == Infinity || number == -Infinity);
};

